<div class="content-section introduction">
    <div class="feature-intro">
        <h1>VirtualScroller</h1>
        <p>VirtualScroller is an efficient way of rendering lists by displaying a small subset of data in the viewport at any time.</p>
    </div>
</div>

<div class="content-section implementation">
    <div class="card">
        <h5>Lazy Loading</h5>
        <p-virtualScroller [value]="virtualProducts" scrollHeight="450px" [itemSize]="100" [rows]="100" 
            [lazy]="true" (onLazyLoad)="loadCarsLazy($event)">
            <ng-template pTemplate="header">
                List of Products
            </ng-template>
            <ng-template let-product pTemplate="item" let-i="index">
                <div class="product-details">
                    <div>
                        <img class="product-item-image" src="assets/showcase/images/demo/product/{{product.image}}">
                        <div>
                            <b>{{product.name}} - ${{product.price}}</b>
                            <span [class]="'product-badge status-'+product.inventoryStatus.toLowerCase()">{{product.inventoryStatus}}</span>
                        </div>
                    </div>
                    <p-button icon="pi pi-search"></p-button>
                </div>
            </ng-template>
            <ng-template let-product pTemplate="loadingItem">
                <div class="product-details">
                    <div>
                        <div class="product-item-image empty-product-item-image"></div>
                        <div>
                            <b class="empty-product-item-text"></b>
                            <div class="empty-product-item-text"></div> 
                        </div>
                    </div>
                    <div class="empty-product-item-button"></div>
                </div>
            </ng-template>
        </p-virtualScroller>
    </div>

    <div class="card">
        <h5>Prepopulated List</h5>
        <p-virtualScroller [value]="products" scrollHeight="500px" [itemSize]="100">
            <ng-template pTemplate="header">
                <div class="list-header">
                    <div class="title-container">
                        List of Products
                    </div>
                    <p-dropdown [options]="sortOptions" [(ngModel)]="sortKey" placeholder="Sort By" (onChange)="onSortChange()" [style]="{'min-width':'10em'}"></p-dropdown>
                </div>
            </ng-template>
            <ng-template let-product pTemplate="item" let-i="index">
                <div class="product-details">
                    <div>
                        <img class="product-item-image" src="assets/showcase/images/demo/product/{{product.image}}">
                        <div>
                            <b>{{product.name}} - ${{product.price}}</b>
                            <span [class]="'product-badge status-'+product.inventoryStatus.toLowerCase()">{{product.inventoryStatus}}</span>
                        </div>
                    </div>
                    <p-button icon="pi pi-search" (click)="selectCar($event, product)"></p-button>
                </div>
            </ng-template>
        </p-virtualScroller>
    </div>
</div>

<div class="content-section documentation">
    <p-tabView>
        <p-tabPanel header="Documentation">
            <h5>CDK</h5>
            <p>VirtualScrolling depends on @angular/cdk's ScrollingModule so begin with installing CDK if not already installed.</p>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
npm install @angular/cdk --save
</app-code>

            <h5>Import</h5>
<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
import &#123;VirtualScrollerModule&#125; from 'primeng/virtualscroller';
</app-code>

            <h5>Getting Started</h5>
            <p>VirtualScroller requires a collection of items as its value, height of an item size, height of the scrollable viewport and a ng-template to display
                    where each item can be accessed using the implicit variable.</p>

            <p>Throughout the samples, a car interface having vin, brand, year and color properties are used
                to define an object to be displayed by the VirtualScroller. Cars are loaded by a CarService that
                connects to a server to fetch the cars with a Promise. Note that this is for demo purposes only, 
                any data source such as an Observable can be used as an alternative as well.</p>
<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export interface Car &#123;
    vin;
    year;
    brand;
    color;
&#125;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
import &#123; HttpClient &#125; from '@angular/common/http';
import &#123; Injectable &#125; from '@angular/core';

import &#123; Car &#125; from '../domain/car';

@Injectable()
export class CarService &#123;

    constructor(private http: HttpClient) &#123;&#125;

    getCarsSmall() &#123;
        return this.http.get('/showcase/resources/data/cars-small.json')
                    .toPromise()
                    .then(res => &lt;Car[]&gt; res.data)
                    .then(data => &#123; return data; &#125;);
    &#125;
&#125;
</app-code>

            <p>Here is a sample VirtualScroller that displays a list of cars loaded from a remote datasource.</p>
<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class VirtualScrollerDemo implements OnInit &#123;

    cars: Car[];

    constructor(private carService: CarService) &#123; &#125;

    ngOnInit() &#123;
        this.carService.getCarsLarge().then(cars => this.cars = cars);
    &#125;
&#125;
</app-code>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-virtualScroller [value]="cars" scrollHeight="500px" [itemSize]="150"&gt;
    &lt;ng-template pTemplate="item" let-car&gt;
        Car content
    &lt;/ng-template&gt;
&lt;/p-virtualScroller&gt;
</app-code>

            <h5>Sections</h5>
            <p>Header and Footer are the two sections that are capable of displaying custom content.</p>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-virtualScroller [value]="cars" scrollHeight="500px" [itemSize]="150"&gt;
    &lt;p-header&gt;Header Content&lt;/p-header&gt;
    &lt;p-footer&gt;Footer Content&lt;/p-footer&gt;
    &lt;ng-template pTemplate="item" let-car&gt;
        Car content
    &lt;/ng-template&gt;
&lt;/p-virtualScroller&gt;
</app-code>

            <h5>Lazy Loading</h5>
            <p>Lazy mode is handy to deal with large datasets where instead of loading the entire data, small chunks of data are loaded on demand by invoking
             onLazyLoad callback everytime scrolling requires a new chunk. To implement lazy loading,
            enable <i>lazy</i> attribute, initialize your data as a placeholder with a length and finally implement a method callback using <i>onLazyLoad</i> that actually loads a chunk from a datasource. onLazyLoad gets an event object
            that contains information about the chunk of data to load such as the index and number of items to load. Notice that a new template called loadingItem is also required to display as a placeholder while the new items are being loaded.</p>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-virtualScroller [value]="virtualCars" scrollHeight="500px" [itemSize]="150" [rows]="100"
    [lazy]="true" (onLazyLoad)="loadCarsLazy($event)"&gt;
    &lt;ng-template let-car pTemplate="item"&gt;
        Car content
    &lt;/ng-template&gt;
    &lt;ng-template let-car pTemplate="loadingItem"&gt;
        Loading...
    &lt;/ng-template&gt;
&lt;/p-virtualScroller&gt;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class LazyVirtualScrollerDemo implements OnInit &#123;

    virtualCars: Car[];
   
    ngOnInit() &#123;
        this.cars = Array.from(&#123;length: 10000&#125;).map(() => this.carService.generateCar());
        this.virtualCars =  Array.from(&#123;length: 10000&#125;);
    &#125;

    loadCarsLazy(event: LazyLoadEvent) &#123;       
        //simulate remote connection with a timeout 
        setTimeout(() => &#123;
            //load data of required page
            let loadedCars = this.cars.slice(event.first, (event.first + event.rows));

            //populate page of virtual cars
            Array.prototype.splice.apply(this.virtualCars, [...[event.first, event.rows], ...loadedCars]);
            
            //trigger change detection
            this.virtualCars = [...this.virtualCars];
        &#125;, 1000);
    &#125;

&#125;
</app-code>

            <h5>Programmatic Scroll</h5>
            <p>Scrolling to a specific index can be done with the <i>scrollToIndex</i> function.</p>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;button pButton label="Reset" (click)="reset"&gt;&lt;/button&gt;

&lt;p-virtualScroller #vs [value]="cars" scrollHeight="500px" [itemSize]="150"&gt;
    &lt;ng-template pTemplate="item" let-car&gt;
        Car content
    &lt;/ng-template&gt;
&lt;/p-virtualScroller&gt;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
@ViewChild('vs') vs: VirtualScroller;

reset() &#123;
    this.vs.scrollToIndex(0, 'smooth');
&#125;
</app-code>      

            <h5>Properties</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Type</th>
                            <th>Default</th>
                            <th>Description</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>cache</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Whether to cache the already loaded data in lazy loading.</td>
                        </tr>
                        <tr>
                            <td>itemSize</td>
                            <td>number</td>
                            <td>null</td>
                            <td>Height of an item in the list.</td>
                        </tr>
                        <tr>
                            <td>lazy</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Defines if data is loaded and interacted with in lazy manner.</td>
                        </tr>
                        <tr>
                            <td>minBufferPx</td>
                            <td>number</td>
                            <td>null</td>
                            <td>Minimum amount of content buffer (in pixels) that the viewport must render.</td>
                        </tr>
                        <tr>
                            <td>maxBufferPx</td>
                            <td>number</td>
                            <td>null</td>
                            <td>Configures how much buffer space to render back up to when it detects that more buffer is required.</td>
                        </tr>
                        <tr>
                            <td>rows</td>
                            <td>number</td>
                            <td>null</td>
                            <td>Number of rows to display per page.</td>
                        </tr>
                        <tr>
                            <td>scrollHeight</td>
                            <td>any</td>
                            <td>null</td>
                            <td>Max height of the content area in inline mode.</td>
                        </tr>
                        <tr>
                            <td>style</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Inline style of the component.</td>
                        </tr>
                        <tr>
                            <td>styleClass</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Style class of the component.</td>
                        </tr>
                        <tr>
                            <td>totalRecords</td>
                            <td>number</td>
                            <td>null</td>
                            <td>Number of total records, defaults to length of value when not defined.</td>
                        </tr>
                        <tr>
                            <td>trackBy</td>
                            <td>Function</td>
                            <td>null</td>
                            <td>Function to optimize the dom operations by delegating to ngForTrackBy, default algoritm checks for object identity.</td>
                        </tr>
                        <tr>
                            <td>value</td>
                            <td>array</td>
                            <td>null</td>
                            <td>An array of objects to display.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Events</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                    <tr>
                        <th>Name</th>
                        <th>Parameters</th>
                        <th>Description</th>
                    </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>onLazyLoad</td>
                            <td>event.first = First row offset <br>
                                event.rows = Number of rows per page <br></td>
                            <td>Callback to invoke in lazy mode to load new data.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Methods</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Parameters</th>
                            <th>Description</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>scrollToIndex</td>
                            <td>index: Index of the item.<br />
                                mode: Scroll mode e.g. 'auto' or 'smooth'
                            </td>
                            <td>Scrolls to the item with the given index.</td>
                        </tr>
                        <tr>
                            <td>clearCache</td>
                            <td>-</td>
                            <td>Clears the loaded items in lazy mode.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Styling</h5>
            <p>Following is the list of structural style classes, for theming classes visit <a href="#" [routerLink]="['/theming']">theming page</a>.</p>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Element</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>p-virtualscroller</td>
                            <td>Container element.</td>
                        </tr>
                        <tr>
                            <td>p-virtualscroller-header</td>
                            <td>Header section.</td>
                        </tr>
                        <tr>
                            <td>p-virtualscroller-footer</td>
                            <td>Footer section.</td>
                        </tr>
                        <tr>
                            <td>p-virtualscroller-content</td>
                            <td>Content section.</td>
                        </tr>
                        <tr>
                            <td>p-virtualscroller-list</td>
                            <td>List element.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Dependencies</h5>
            <p>Angular CDK.</p>
        </p-tabPanel>

        <p-tabPanel header="Source">
            <a href="https://github.com/primefaces/primeng/tree/master/src/app/showcase/components/virtualscroller" class="btn-viewsource" target="_blank">
                <span>View on GitHub</span>
            </a>
            <a href="https://stackblitz.com/edit/primeng-virtualscroller-demo" class="btn-viewsource" style="margin-left: .5em;" target="_blank">
                <span>Edit in StackBlitz</span>
            </a>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;h5&gt;Lazy Loading&lt;/h5&gt;
&lt;p-virtualScroller [value]="virtualProducts" scrollHeight="450px" [itemSize]="100" [rows]="100" 
        [lazy]="true" (onLazyLoad)="loadCarsLazy($event)"&gt;
    &lt;ng-template pTemplate="header"&gt;
        List of Products
    &lt;/ng-template&gt;
    &lt;ng-template let-product pTemplate="item" let-i="index"&gt;
        &lt;div class="car-details"&gt;
            &lt;div&gt;
                &lt;img class="car-item-image" src="assets/showcase/images/demo/product/&#123;&#123;product.image&#123;&#123;"&gt;
                &lt;div&gt;
                    &lt;b&gt;&#123;&#123;product.name&#123;&#123; - $&#123;&#123;product.price&#123;&#123;&lt;/b&gt;
                    &lt;span [class]="'product-badge status-'+product.inventoryStatus.toLowerCase()"&gt;&#123;&#123;product.inventoryStatus&#123;&#123;&lt;/span&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;p-button icon="pi pi-search" (click)="selectCar($event, car)"&gt;&lt;/p-button&gt;
        &lt;/div&gt;
    &lt;/ng-template&gt;
    &lt;ng-template let-car pTemplate="loadingItem"&gt;
        &lt;div class="car-details"&gt;
            &lt;div&gt;
                &lt;div class="car-item-image empty-car-item-image"&gt;&lt;/div&gt;
                &lt;div&gt;
                    &lt;b class="empty-car-item-text"&gt;&lt;/b&gt;
                    &lt;div class="empty-car-item-text"&gt;&lt;/div&gt; 
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class="empty-car-item-button"&gt;&lt;/div&gt;
        &lt;/div&gt;
    &lt;/ng-template&gt;
&lt;/p-virtualScroller&gt;


&lt;h5&gt;Prepopulated List&lt;/h5&gt;
&lt;p-virtualScroller [value]="products" scrollHeight="500px" [itemSize]="100"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;div class="list-header"&gt;
            &lt;div class="title-container"&gt;
                List of Products
            &lt;/div&gt;
            &lt;p-dropdown [options]="sortOptions" [(ngModel)]="sortKey" placeholder="Sort By" (onChange)="onSortChange()" [style]="&#123;'min-width':'10em'&#123;"&gt;&lt;/p-dropdown&gt;
        &lt;/div&gt;
    &lt;/ng-template&gt;
    &lt;ng-template let-product pTemplate="item" let-i="index"&gt;
        &lt;div class="car-details"&gt;
            &lt;div&gt;
                &lt;img class="car-item-image" src="assets/showcase/images/demo/product/&#123;&#123;product.image&#123;&#123;"&gt;
                &lt;div&gt;
                    &lt;b&gt;&#123;&#123;product.name&#123;&#123; - $&#123;&#123;product.price&#123;&#123;&lt;/b&gt;
                    &lt;span [class]="'product-badge status-'+product.inventoryStatus.toLowerCase()"&gt;&#123;&#123;product.inventoryStatus&#123;&#123;&lt;/span&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;p-button icon="pi pi-search" (click)="selectCar($event, product)"&gt;&lt;/p-button&gt;
        &lt;/div&gt;
    &lt;/ng-template&gt;
&lt;/p-virtualScroller&gt;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
import &#123;Component,OnInit} from '@angular/core';
import &#123;CarService} from '../../service/carservice';
import &#123;LazyLoadEvent,SelectItem} from 'primeng/api';
import &#123; Product } from '../../domain/product';
import &#123; ProductService } from '../../service/productservice';

@Component(&#123;
    templateUrl: './virtualscrollerdemo.html',
    styles: [`
        .product-details &#123;
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 20px;
        }

        .product-details &gt; div &#123;
            display: flex;
            align-items: center;
        }

        .product-item-image &#123;
            margin-right: 14px;
            width: 60px;
            height: 60px;
        }

        .empty-product-item-image &#123;
            background-color: #f1f1f1;
            animation: pulse 1s infinite ease-in-out;
            margin-right: 14px;
            border-radius: 3px;
        }

        .empty-product-item-text &#123;
            background-color: #f1f1f1;
            height: 19px;
            animation: pulse 1s infinite ease-in-out;
            display: block;
            width: 100px;
            margin-bottom: 2px;
            border-radius: 3px;
        }

        .empty-product-item-button &#123;
            background-color: #f1f1f1;
            height: 33px;
            width: 33px;
            animation: pulse 1s infinite ease-in-out;
            display: block;
            border-radius: 3px;
        }

        .list-header &#123;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }

        .title-container &#123;
            text-align: left;
        }

        .sort-container &#123;
            text-align: right;
        }

        @media (max-width: 40em) &#123;
            .product-item &#123;
                text-align: center;
            }
        }
    `]
})
export class VirtualScrollerDemo implements OnInit &#123;

    products: Product[];

    virtualProducts: Product[];

    sortKey: string;

    sortOptions: SelectItem[];

    constructor(private carService: CarService, private productService: ProductService) &#123;}

    ngOnInit() &#123;
        this.products = Array.from(&#123;length: 10000}).map(() =&gt; this.productService.generatePrduct());
        this.virtualProducts = Array.from(&#123;length: 10000});

        this.sortOptions = [
            &#123;label: 'Cheapest First', value: 'price'},
            &#123;label: 'Expensive First', value: '!price'}
        ];
    }

    loadCarsLazy(event: LazyLoadEvent) &#123;       
        // simulate remote connection with a timeout 
        setTimeout(() =&gt; &#123;
            //load data of required page
            let loadedProducts = this.products.slice(event.first, (event.first + event.rows));

            //populate page of virtual cars
            Array.prototype.splice.apply(this.virtualProducts, [...[event.first, event.rows], ...loadedProducts]);
            
            //trigger change detection
            this.virtualProducts = [...this.virtualProducts];
        }, 1000);
    }

    onSortChange() &#123;
        if (this.sortKey.indexOf('!') === 0)
            this.sort(-1);
        else
            this.sort(1);
    }

    sort(order: number): void &#123;
        let products = [...this.products];
        products.sort((data1, data2) =&gt; &#123;
            let value1 = data1.price;
            let value2 = data2.price;
            let result = (value1 &lt; value2) ? -1 : (value1 &gt; value2) ? 1 : 0;

            return (order * result);
        });

        this.products = products;
    }
}
</app-code>
        </p-tabPanel>
        <p-tabPanel header="StackBlitz">
            <ng-template pTemplate="content">
                <iframe src="https://stackblitz.com/edit/primeng-virtualscroller-demo?embed=1" style="width: 100%; height: 768px; border: none;"></iframe>
            </ng-template>
        </p-tabPanel>
    </p-tabView>
</div>